#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sikle.h"

/* reverse:  reverse string s in place */
static void reverse(char s[])
{
  int i, j;
  char c;
 
  for (i = 0, j = strlen(s)-1; i<j; i++, j--) {
    c = s[i];
    s[i] = s[j];
    s[j] = c;
  }
}

/* itoa:  convert n to characters in s */
static char* itoa(int n, char s[])
{
  int i, sign;
 
  if ((sign = n) < 0)  /* record sign */
    n = -n;            /* make n positive */
  i = 0;
  do {                 /* generate digits in reverse order */
    s[i++] = n % 10 + '0';   /* get next digit */
  } while ((n /= 10) > 0);     /* delete it */
  if (sign < 0)
    s[i++] = '-';
  s[i] = '\0';
  reverse(s);
  return s;
}

void SInteger_push(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp;

  //  printf("SInteger_push(base:%p,sp:%d)\n", es->base, es->sp);
  if(stacel->type == STACEL_INTEGER) {
    *(SInteger*)sp = *(SInteger*)stacel;
    sp += sizeof(SInteger);
    STACEL(sp)->type = STACEL_INTEGER;
    es->sp += sizeof(SInteger) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SInteger_push error", NULL);
  }
}

void SInteger_pop(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_INTEGER) {
    sp -= sizeof(SInteger);
    *(SInteger*)stacel = *(SInteger*)sp;
    es->sp = sp - es->base;
  }
  else {
    log_msg(LOG_ERROR, "SInteger_pop error", NULL);
  }
}

void SInteger_drop(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_INTEGER) {
    es->sp -= sizeof(SInteger) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SInteger_drop error", NULL);
  }
}

void SInteger_dup(SikleState* ss)
{
  SInteger integer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_INTEGER) {
    sp -= sizeof(SInteger);
    integer = *(SInteger*)sp;

    sp = es->base + es->sp;
    *(SInteger*)sp = integer;
    sp += sizeof(SInteger);
    STACEL(sp)->type = STACEL_INTEGER;
    es->sp += sizeof(SInteger) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SInteger_dup error", NULL);
  }
}

void SInteger_p(SikleState* ss)
{
  SInteger integer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_INTEGER) {
    sp -= sizeof(SInteger);
    integer = *(SInteger*)sp;

    printf("%d", integer.value);
  }
  else {
    log_msg(LOG_ERROR, "SInteger_p error", NULL);
  }
}

int SInteger_to_integer(SikleState* ss)
{
  SInteger integer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_INTEGER) {
    sp -= sizeof(SInteger);
    integer = *(SInteger*)sp;

    return integer.value;
  }
  else {
    log_msg(LOG_ERROR, "SInteger_p error", NULL);
    return 0;
  }
}

SPString SInteger_to_string(SikleState* ss)
{
  SInteger integer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  SPString sps;

  if(STACEL(sp)->type == STACEL_INTEGER) {
    char buff[50];

    sp -= sizeof(SInteger);
    integer = *(SInteger*)sp;

    itoa(integer.value, buff);
    sps = SPString_new(ss->string_pool, buff, strlen(buff));
    
    return sps;
  }
  else {
    log_msg(LOG_ERROR, "SInteger_p error", NULL);
  }

  return sps;
}

int SInteger_to_boolean(SikleState* ss)
{
  SInteger integer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_INTEGER) {
    sp -= sizeof(SInteger);
    integer = *(SInteger*)sp;

    return integer.value;
  }
  else {
    log_msg(LOG_ERROR, "SInteger_p error", NULL);
    return 0;
  }
}

void SInteger_add(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i0.value += i1.value;
    SInteger_push(ss, STACEL(&i0));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_add error", NULL);
  }
}

void SInteger_sub(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value -= i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_sub error", NULL);
  }
}

void SInteger_mul(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value *= i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_mul error", NULL);
  }
}

void SInteger_div(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value /= i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_div error", NULL);
  }
}

void SInteger_gt(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value = i1.value > i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_gt error", NULL);
  }
}

void SInteger_lt(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value = i1.value < i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_lt error", NULL);
  }
}

void SInteger_ngt(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value = i1.value <= i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_ngt error", NULL);
  }
}

void SInteger_nlt(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value = i1.value >= i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_nlt error", NULL);
  }
}

void SInteger_equ(SikleState* ss)
{
  SInteger i0;
  SInteger i1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    SInteger_pop(ss, STACEL(&i0));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SInteger_pop(ss, STACEL(&i1));

    i1.value = i1.value == i0.value;
    SInteger_push(ss, STACEL(&i1));
  }
  else {
    log_msg(LOG_ERROR, "SInteger_equ error", NULL);
  }
}

StacelOperation integer_ops = {
  .push = SInteger_push,
  .pop = SInteger_pop,
  .drop = SInteger_drop,
  .dup = SInteger_dup,

  .clone = NULL,

  .to_integer = SInteger_to_integer,
  .to_string = SInteger_to_string,
  .to_boolean = SInteger_to_boolean,

  .p = SInteger_p,

  .add = SInteger_add,
  .sub = SInteger_sub,
  .mul = SInteger_mul,
  .div = SInteger_div,
  .gt = SInteger_gt,
  .lt = SInteger_lt,
  .ngt = SInteger_ngt,
  .nlt = SInteger_nlt,
  .equ = SInteger_equ
};
